%%%-------------------------------------------------------------------
%%% @author glendy
%%% @copyright (C) 2025, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 23. 9月 2025 8:00
%%%-------------------------------------------------------------------
-module(redis_decode).
-author("glendy").

-include("redis_driver.hrl").

%% API
-export([
  init/0,
  decode/1
]).

init() ->
  #pstate{}.

get_newline_pos(BinaryData) ->
  case re:run(BinaryData, ?NL) of
    {match, [{Pos, _}]} -> Pos;
    nomatch -> undefined
  end.

%% 返回：{ok, Value, #pstate{}} | {continue, #pstate{}} | {error, unknown_response}
decode(#pstate{state = undefined, rest_bin = RestData} = PState) ->
  NewLinePos = get_newline_pos(RestData),
  if
    NewLinePos =/= undefined ->
      FirstFieldPos = NewLinePos - 1,
      case RestData of
        <<$+, StrBin:FirstFieldPos/binary, ?NL, NewRestData/binary>> ->
          NewPState = PState#pstate{
            state = undefined,
            rest_bin = NewRestData,
            pdata = #pdata{}
          },
          {ok, StrBin, NewPState};
        <<$-, StrBin:FirstFieldPos/binary, ?NL, NewRestData/binary>> ->
          NewPState = PState#pstate{
            state = undefined,
            rest_bin = NewRestData,
            pdata = #pdata{}
          },
          {error, StrBin, NewPState};
        <<$:, IntBin:FirstFieldPos/binary, ?NL, NewRestData/binary>> ->
          NewPState = PState#pstate{
            state = undefined,
            rest_bin = NewRestData,
            pdata = #pdata{}
          },
          {ok, list_to_integer(binary_to_list(IntBin)), NewPState};
        <<$$, IntBin:FirstFieldPos/binary, ?NL, NewRestData/binary>> ->
          StrLen = list_to_integer(binary_to_list(IntBin)),
          if
            StrLen =< -1 ->
              NewPState = PState#pstate{
                state = undefined,
                rest_bin = NewRestData,
                pdata = #pdata{}
              },
              {ok, <<>>, NewPState};
            true ->
              case NewRestData of
                <<StrBin:StrLen/binary, ?NL, NewRestData1/binary>> ->
                  NewPState = PState#pstate{
                    state = undefined,
                    rest_bin = NewRestData1,
                    pdata = #pdata{}
                  },
                  {ok, StrBin, NewPState};
                _ ->
                  NewPdata = #pdata{
                    data_type = redis_multiline,
                    is_finish = false,
                    multiline_str_size = StrLen,
                    str_val = <<>>
                  },
                  NewPState = PState#pstate{
                    state = redis_multiline,
                    rest_bin = NewRestData,
                    pdata = NewPdata
                  },
                  {continue, NewPState}
              end
          end;
        <<$*, IntBin:FirstFieldPos/binary, ?NL, NewRestData/binary>> ->
          ListLen = list_to_integer(binary_to_list(IntBin)),
          if
            ListLen =< -1 ->
              NewPState = PState#pstate{
                state = undefined,
                rest_bin = NewRestData,
                pdata = #pdata{}
              },
              {ok, [], NewPState};
            true ->
              NewPdata = #pdata{
                data_type = redis_list,
                is_finish = false,
                list_size = ListLen,
                list_val = []
              },
              case loop_decode_array(ListLen, NewRestData, NewPdata) of
                {ok, ArrayData, NewRestData1} ->
                  {ok, ArrayData, PState#pstate{state = undefined, rest_bin = NewRestData1, pdata = #pdata{}}};
                {continue, NewRestData1, NewPdata1} ->
                  {continue, PState#pstate{state = redis_list, rest_bin = NewRestData1, pdata = NewPdata1}}
              end
          end;
        _ ->
          %% response, but cannot make any sense of it
          {error, unknown_response, init()}
      end;
    true ->
      {continue, PState}
  end;
decode(#pstate{state = redis_multiline, rest_bin = RestData, pdata = PData} = PState) ->
  #pdata{
    multiline_str_size = StrLen
  } = PData,
  case RestData of
    <<StrBin:StrLen/binary, ?NL, NewRestData/binary>> ->
      NewPState = PState#pstate{
        state = undefined,
        rest_bin = NewRestData,
        pdata = #pdata{}
      },
      {ok, StrBin, NewPState};
    _ ->
      {continue, PState}
  end;
decode(#pstate{state = redis_list, rest_bin = RestData, pdata = PData} = PState) ->
  #pdata{
    data_type = redis_list,
    list_size = DataSize,
    list_val = DataList
  } = PData,
  DataListLen = length(DataList),
  if
    DataListLen > 0 ->
      [FirstValue | RestDataList] = DataList,
      case FirstValue of
        #pdata{data_type = FirstDataType, is_finish = false} ->
          case decode(#pstate{state = FirstDataType, rest_bin = RestData, pdata = FirstValue}) of
            {ok, NewFirstValue, NewFirstPState} ->
              NewDataList = [NewFirstValue | RestDataList],
              NewPData = PData#pdata{
                list_val = NewDataList
              },
              case loop_decode_array(DataSize - DataListLen, NewFirstPState#pstate.rest_bin, NewPData) of
                {ok, NewNextValue, NewRestData} ->
                  {ok, NewNextValue, PState#pstate{state = undefined, rest_bin = NewRestData, pdata = #pdata{}}};
                {continue, NewRestData, NewNextPData} ->
                  {continue, PState#pstate{state = redis_list, rest_bin = NewRestData, pdata = NewNextPData}}
              end;
            {continue, NewFirstPState} ->
              NewFirstPData0 = NewFirstPState#pstate.pdata,
              NewFirstPData = NewFirstPData0#pdata{
                data_type = NewFirstPState#pstate.state,
                is_finish = false
              },
              NewDataList = [NewFirstPData | RestDataList],
              NewPData = PData#pdata{
                list_val = NewDataList
              },
              {continue, PState#pstate{rest_bin = NewFirstPState#pstate.rest_bin, pdata = NewPData}}
          end;
        _Other ->
          case loop_decode_array(DataSize - DataListLen, RestData, PData) of
            {ok, NewNextValue, NewRestData} ->
              {ok, NewNextValue, PState#pstate{state = undefined, rest_bin = NewRestData, pdata = #pdata{}}};
            {continue, NewRestData, NewNextPData} ->
              {continue, PState#pstate{state = redis_list, rest_bin = NewRestData, pdata = NewNextPData}}
          end
      end;
    true ->
      case loop_decode_array(DataSize, RestData, PData) of
        {ok, NewNextValue, NewRestData} ->
          {ok, NewNextValue, PState#pstate{state = undefined, rest_bin = NewRestData, pdata = #pdata{}}};
        {continue, NewRestData, NewNextPData} ->
          {continue, PState#pstate{state = redis_list, rest_bin = NewRestData, pdata = NewNextPData}}
      end
  end.

%% 返回： {ok, Value, RestBin} | {continue, RestBin, #pdata{}} | {error, unknown_response, #pstate{}}
loop_decode_array(DataSize, RestData, PData) when DataSize =< 0 ->
  Value = combine_pdata(PData),
  {ok, Value, RestData};
loop_decode_array(DataSize, RestData, PDataAcc) ->
  TmpPState = #pstate{
    state = undefined,
    rest_bin = RestData,
    pdata = #pdata{}
  },
  case decode(TmpPState) of
    {ok, DataValue, NewTmpPState} ->
      NewPDataAcc = PDataAcc#pdata{
        list_val = [DataValue | PDataAcc#pdata.list_val]
      },
      loop_decode_array(DataSize - 1, NewTmpPState#pstate.rest_bin, NewPDataAcc);
    {continue, NewTmpPState} ->
      NewTmpPdata = NewTmpPState#pstate.pdata,
      if
        NewTmpPdata#pdata.data_type =/= undefined ->
          NewPDataAcc = PDataAcc#pdata{
            list_val = [NewTmpPdata | PDataAcc#pdata.list_val]
          },
          {continue, NewTmpPState#pstate.rest_bin, NewPDataAcc};
        true ->
          {continue, NewTmpPState#pstate.rest_bin, PDataAcc}
      end;
    Other ->
      Other
  end.

combine_pdata(#pdata{data_type=redis_str, str_val=StrVal}) ->
  StrVal;
combine_pdata(#pdata{data_type=redis_error, str_val=StrVal}) ->
  StrVal;
combine_pdata(#pdata{data_type=redis_int, int_val=IntVal}) ->
  IntVal;
combine_pdata(#pdata{data_type=redis_multiline, str_val=StrVal}) ->
  StrVal;
combine_pdata(#pdata{data_type=redis_list, list_val=ListVal}) ->
  lists:reverse([combine_pdata(Val) || Val <- ListVal]);
combine_pdata(Value) ->
  Value.

